package com.medusa.aps.business.modules.modeling.mp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.medusa.aps.business.model.constant.RedisConstants;
import com.medusa.aps.business.model.enums.ProductionType;
import com.medusa.aps.business.model.exception.BusinessErrorCode;
import com.medusa.aps.business.model.pagination.Page;
import com.medusa.aps.business.modules.basic.model.output.MaterialChooseVO;
import com.medusa.aps.business.modules.basic.mp.entity.Material;
import com.medusa.aps.business.modules.basic.mp.service.MaterialService;
import com.medusa.aps.business.modules.modeling.model.input.*;
import com.medusa.aps.business.modules.modeling.model.output.BomItemVo;
import com.medusa.aps.business.modules.modeling.model.output.BomVo;
import com.medusa.aps.business.modules.modeling.mp.entity.Bom;
import com.medusa.aps.business.modules.modeling.mp.entity.BomItem;
import com.medusa.aps.business.modules.modeling.mp.mapper.BomMapper;
import com.medusa.aps.business.modules.modeling.mp.service.BomItemService;
import com.medusa.aps.business.modules.modeling.mp.service.BomService;
import com.medusa.aps.business.common.redis.annotation.Redisson;
import com.medusa.aps.business.global.model.exception.GlobalException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author miskw
 * @date 2023/8/23
 */
@Slf4j
@Service("bomService")
public class BomServiceImpl extends ServiceImpl<BomMapper, Bom> implements BomService {
    @Resource
    private BomItemService bomItemService;
    @Resource
    private MaterialService materialService;


    @Override
    public Page<Bom> selectPageList(Page<Bom> page, BomParam bomParam) {
        return baseMapper.selectPageList(page, bomParam);
    }

    @Override
    public Page<MaterialChooseVO> chooseMaterial(ChooseMaterialRouteParam chooseMaterialRouteParam) {
        return baseMapper.chooseMaterial(chooseMaterialRouteParam, new Page<>(chooseMaterialRouteParam.getCurrent(), chooseMaterialRouteParam.getSize()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.MATERIAL_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.BOM_LOCK_KEY, waitTime = 1)
    public void saveBom(BomDTO bomDTO) {
        bomDTO.validParam();
        saveBomValid(bomDTO);
        saveBom(bomDTO, false);
    }

    @Override
    public BomVo selectBomDetail(BomDetailParam bomDetailParam) {
        Bom bom = lambdaQuery()
                .eq(bomDetailParam.getId() != null, Bom::getId, bomDetailParam.getId())
                .eq(StrUtil.isNotEmpty(bomDetailParam.getMaterialCode()), Bom::getMaterialCode, bomDetailParam.getMaterialCode())
                .one();
        if (BeanUtil.isEmpty(bom)) {
            throw BusinessErrorCode.DATA_IS_EMPTY.exception();
        }
        List<BomItem> bomItems = bomItemService.selectBomItemByBomId(bom.getId());
        BomVo bomVo = bom.toBomVo();
        if (CollUtil.isEmpty(bomItems)) {
            return bomVo;
        }
        Map<Long, List<BomItem>> bomItemParentMap = bomItems.stream().collect(Collectors.groupingBy(BomItem::getParentId));
        bomVo.setChildren(
                handlerBomItemChildren(bomVo.getMaterialCode(), bomVo.getMaterialName(), bomItemParentMap.get(0L), bomItemParentMap)
        );
        return bomVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.BOM_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.MATERIAL_LOCK_KEY, waitTime = 1)
    public void updateBom(BomDTO bomDTO) {
        Bom preBom = validUpdateBom(bomDTO);
        Bom afterBom = bomDTO.toBom();
        afterBom.setBomCode(preBom.getBomCode());
        updateById(afterBom);
        bomItemService.deletedBomItemByBomId(List.of(preBom.getId()));
       saveBom(bomDTO, true);
    }

    private Bom validUpdateBom(BomDTO bomDTO) {
        Bom bom = lambdaQuery()
                .select(Bom::getId, Bom::getBomCode, Bom::getBomName, Bom::getMaterialCode)
                .eq(Bom::getId, bomDTO.getId())
                .oneOpt().orElseThrow(() -> new GlobalException(BusinessErrorCode.DATA_IS_EMPTY.getCode(), "当前BOM数据不存在"));
        if (!bom.getMaterialCode().equals(bomDTO.getMaterialCode())) {
            throw new GlobalException(BusinessErrorCode.DATA_IS_NOT_EMPTY.getCode(), StrUtil.format("父项物料编码:【{}】不允许被编辑", bomDTO.getMaterialCode()));
        }
        validMaterial(bomDTO);
        return bom;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.BOM_LOCK_KEY, waitTime = 1)
    public void deleteBomByIds(List<Integer> bomIds) {
        List<Bom> bomList = lambdaQuery()
                .select(Bom::getId, Bom::getBomCode, Bom::getMaterialCode)
                .in(Bom::getId, bomIds)
                .list();
        if (CollUtil.isEmpty(bomList) || bomIds.size() != bomList.size()) {
            throw BusinessErrorCode.DATA_IS_EMPTY.exception();
        }
        List<Integer> bomIdList = bomList.stream()
                .map(Bom::getId)
                .toList();
        removeByIds(bomIds);
        bomItemService.deletedBomItemByBomId(bomIdList);
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveBom(BomDTO bomDTO, Boolean isUpdate) {
        if (!isUpdate) {
            Bom bom = bomDTO.toBom();
            save(bom);
            bomDTO.setId(bom.getId());
        }
        List<BomItem> bomItems = new ArrayList<>();
        for (BomItemDTO child : bomDTO.getChildren()) {
            BomItem bomItem = child.toBeanBomItem(bomDTO.getId(), bomDTO.getBomCode());
            bomItem.setParentId(0L);
            if (CollUtil.isNotEmpty(child.getChildren())) {
                //生成子项bomItem
                generateBomItem(bomDTO.getId(), bomDTO.getBomCode(), bomItems, bomItem.getId(), child.getChildren());
            }
            bomItems.add(bomItem);
        }
        bomItemService.saveBatch(bomItems);
    }

    /**
     * 校验BOM编码或父项物料编码是否被使用
     *
     * @param bomDTO BOM信息
     */
    private void saveBomValid(BomDTO bomDTO) {
        lambdaQuery()
                .select(Bom::getBomCode)
                .eq(Bom::getBomCode, bomDTO.getBomCode())
                .oneOpt()
                .ifPresent(item -> {
                    throw new GlobalException(BusinessErrorCode.DATA_IS_NOT_EMPTY.getCode(), StrUtil.format("BOM编码【{}】系统已存在", bomDTO.getBomCode()));
                });
        lambdaQuery()
                .select(Bom::getMaterialCode)
                .eq(Bom::getMaterialCode, bomDTO.getMaterialCode())
                .oneOpt()
                .ifPresent(item -> {
                    throw new GlobalException(BusinessErrorCode.DATA_IS_NOT_EMPTY.getCode(), StrUtil.format("父项物料编码【{}】系统已存在", bomDTO.getMaterialCode()));
                });
        validMaterial(bomDTO);
    }

    /**
     * 校验BOM中所有的物料在数据库中是否存在
     *
     * @param bomDTO 参数
     */
    private void validMaterial(BomDTO bomDTO) {
        Map<String, Long> map = new HashMap<>();
        handlerMaterial(bomDTO.getMaterialCode(), bomDTO.getChildren(), map);
        map.put(bomDTO.getMaterialCode(), bomDTO.getMaterialId());
        List<Material> materials = materialService.lambdaQuery()
                .select(Material::getId, Material::getMaterialCode, Material::getProductionType)
                .in(Material::getMaterialCode, map.keySet())
                .list();
        if (CollUtil.isEmpty(materials)) {
            throw new GlobalException(BusinessErrorCode.DATA_IS_EMPTY.getCode(), "所添加的物料编码均不存在");
        }
        Map<String, Material> materialMap = materials.stream().collect(Collectors.toMap(Material::getMaterialCode, item -> item));
        for (Map.Entry<String, Long> bomMaterial : map.entrySet()) {
            Material material = materialMap.get(bomMaterial.getKey());
            if (BeanUtil.isEmpty(material)) {
                throw new GlobalException(BusinessErrorCode.DATA_IS_EMPTY.getCode(), StrUtil.format("物料编码：{}系统不存在", bomMaterial.getKey()));
            }
            if (!material.getId().equals(bomMaterial.getValue())) {
                throw new GlobalException(BusinessErrorCode.DATA_IS_EMPTY.getCode(), StrUtil.format("物料编码id非法:{}", bomMaterial.getKey()));
            }
        }
        if (!ProductionType.SELF_MADE.getValue().equals(materialMap.get(bomDTO.getMaterialCode()).getProductionType().getValue())) {
            throw new GlobalException(BusinessErrorCode.PARAMETER_ERROR.getCode(), StrUtil.format("父项物料编码:{}不能为非自制", bomDTO.getMaterialCode()));
        }
    }

    /**
     * 抽取BOM里所有子项的物料编码与物料id
     * 校验子项物料编码不能与父项物料编码相同
     * 校验子项物料是否重复
     *
     * @param materialCode 父项物料编码
     * @param children     子项物料
     * @param map          物料编码+物料id
     */
    private void handlerMaterial(String materialCode, List<BomItemDTO> children, Map<String, Long> map) {
        if (CollUtil.isNotEmpty(children)) {
            for (BomItemDTO child : children) {
                if (child.getMaterialCode().equals(materialCode)) {
                    throw new GlobalException(BusinessErrorCode.PARAMETER_ERROR.getCode(), StrUtil.format("子项物料编码:【{}】不能与父项物料编码相同", materialCode));
                }
                if (map.containsKey(child.getMaterialCode())) {
                    throw new GlobalException(BusinessErrorCode.PARAMETER_ERROR.getCode(), StrUtil.format("子项物料编码:【{}】 重复", child.getMaterialCode()));
                }
                map.put(child.getMaterialCode(), child.getMaterialId());
                if (CollUtil.isNotEmpty(child.getChildren())) {
                    if (!ProductionType.SELF_MADE.getValue().equals(child.getProductionType().getValue())) {
                        throw new GlobalException(BusinessErrorCode.PARAMETER_ERROR.getCode(), StrUtil.format("当前物料:【{}】包含子项的，不能为非自制", child.getMaterialCode()));
                    }
                    handlerMaterial(materialCode, child.getChildren(), map);
                }
            }
        }
    }

    private List<BomItemVo> handlerBomItemChildren(String materialCode, String materialName, List<BomItem> bomItems, Map<Long, List<BomItem>> parentItemIdGroupMap) {
        return bomItems.stream().map(bomItem -> {
            BomItemVo bomItemVo = bomItem.toBomItemVo(materialCode, materialName);
            List<BomItem> childrenBomItem = parentItemIdGroupMap.get(bomItem.getId());
            if (CollUtil.isNotEmpty(childrenBomItem)) {
                bomItemVo.setChildren(handlerBomItemChildren(bomItemVo.getMaterialCode(), bomItemVo.getMaterialName(),
                        childrenBomItem, parentItemIdGroupMap));
            }
            return bomItemVo;
        }).collect(Collectors.toList());
    }

    private void generateBomItem(Integer bomId, String bomNo, List<BomItem> bomItems, Long parentId, List<BomItemDTO> children) {
        for (BomItemDTO child : children) {
            BomItem bomItem = child.toBeanBomItem(bomId, bomNo);
            bomItem.setParentId(parentId);
            if (CollUtil.isNotEmpty(child.getChildren())) {
                generateBomItem(bomId, bomNo, bomItems, bomItem.getId(), child.getChildren());
            }
            bomItems.add(bomItem);
        }
    }

}
